MachO 文件结构详解

概念——什么是MachO?

  苹果开发者对它一定不陌生,特别是喜欢逆向的同学,对它的研究更是必不可少。在可安装的每一个.app包中,都有一个与app同名的可执行文件,它可能长这样:(如果你碰到显示为白色的MachO文件,说明当前用户对其没有可执行权限

  MachO其实是Mach Object的缩写,是在Mac以及iOS上可执行的一种文件格式,包括但不限于:可执行文件(.out .o)、动态库、静态库、dyld、目标文件,官方文档中列举如下:
它类似于Windows上的PE、Linux上的ELF格式,我们用file命令看上图中 AlipayWallet 的文件类型信息:
可以看到,该示例的MachO文件属于通用二进制文件(苹果提出的一种能同时适用多种架构二进制文件的程序代码),并支持在两种架构上运行:arm_v7和arm64,包含了多种架构的MachO文件可以通过lipo thin命令进行拆分:lipo AlipayWallet -thin armv7 -output alipayArmv7,同样也能用lipo create进行合并:lipo alipayArm64 alipayArmv7 -create -output AlipayWalletNew,大家可以自己实践一下。

构造——MachO的内部结构是怎样的?

一:简单的讲,它包括以下四个组成部分:

  • Header (头部)
    • 用于快速确认该文件的CPU类型、文件类型等信息
  • LoadCommands (加载命令)
    • 用于告诉loader如何设置并加载二进制数据
  • Data (数据段 segment)
    • 存放数据:代码、字符常量、类、方法等
    • 可以拥有多个segment,每个segment可以有零到多个section。每个段都有一段虚拟地址映射到进程的地址空间
  • Loader Info (链接信息)
    • 一个完整的用户级MachO文件的末端是一系列链接信息。其中包含了动态加载器用来链接可执行文件或者依赖所需使用的符号表、字符串表等

  来用MachOView验证一下该示例的MachO文件结构:

二:逐一探索:

1.Header

  除了用MachOView能查看MachO文件信息,还可以通过otool命令查看,我们先来分析Header中的内容:otool -h AlipayWallet

  • magic:MachO文件的魔数,用来确定其属于64位(0xfeedfacf)还是32位(0xfeedface)例子中有两个Header,分别对应的是armv7和arm64的Header,前者32位,后者64位。
  • cputype和cupsubtype代表的是cpu的类型和其子类型,例子中分别是12(c)与9、16777228(100000c)与0定义如下:
1
2
3
4
#define CPU_TYPE_ARM((cpu_type_t) 12)
#define CPU_SUBTYPE_ARM_V7((cpu_subtype_t) 9)
#define CPU_TYPE_ARM64((cpu_type_t) 16777228)
#define CPU_SUBTYPE_AR64M_ALL((cpu_subtype_t) 0)
  • 接着是filetype,2,代表可执行的文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #define	MH_EXECUTE	0x2		/* demand paged executable file */
    ```
    * ncmds 指的是加载命令(load commands)的数量,例子中一共75个,编号0-74
    * sizeofcmds 表示75个load commands的总字节大小, load commands区域是紧接着header区域的
    * 最后个flags 标识二进制文件支持的功能,主要与系统的加载、链接有关。

    这里为你准备了 mach_header 的苹果官方文档说明:(更多详细定义请参考[loader.h](https://opensource.apple.com/source/xnu/xnu-1456.1.26/EXTERNAL_HEADERS/mach-o/loader.h))
    ![](https://user-gold-cdn.xitu.io/2019/2/21/1690f152b4cd7c0e?w=896&h=736&f=png&s=192704)
    #### 2.LoadCommands
    我们继续用命令查看:`otool -l alipayArm64`,该示例共有75个加载指令,我们只截取一个作为代表,分别为出现在segment和section中的每一个参数进行注释:

Load command 1
cmd LC_SEGMENT_64 // cmd 是load command的类型,LC_SEGMENT_64的含义是将这个64位的段映射到进程地址空间,即加载命令
cmdsize 712 // 代表load command的大小
segname TEXT // 16字节的段名字 TEXT
vmaddr 0x0000000100000000 // 段的虚拟内存起始地址
vmsize 0x00000000036a4000 // 段的虚拟内存大小
fileoff 0 // 段在文件中的偏移量
filesize 57294848 // 段在文件中的大小
maxprot 0x00000005 // 段页面所需要的最高内存保护(4=r,2=w,1=x)
initprot 0x00000005 // 段页面初始的内存保护
nsects 8 // 段中包含section的数量
flags 0x0 // 其他杂项标志位
Section
sectname text // 第一个是text ,就是主程序代码
segname TEXT // 该section所属的 segment名,第一个是TEXT
addr 0x0000000100006110 // 该section在内存的启始位置,0x100006110
size 0x000000000358a268 // size 该section的大小,0x358a268
offset 24848 // 24848 0x6110
align 2^4 (16) // 字节大小对齐,16
reloff 0 // 重定位入口的文件偏移 0
nreloc 0 // 需要重定位的入口数量 0
flags 0x80000400 // 包含section的type和attributes
reserved1 0 // …保留用
reserved2 0 // …保留用
`
注释完毕,我又为你准备了 segment和section 的苹果官方文档说明:(更多详细定义请参考loader.h


  我总结了最常见的加载命令如下:

  • LC_SEGMENT_64: 将该段(64位)映射到进程地址空间中
  • LC_DYLD_INFO_ONLY:加载动态链接库信息(重定向地址、弱引用绑定、懒加载绑定、开放函数等的偏移值等信息)
  • LC_SYMTAB:载入符号表地址
  • LC_DYSYMTAB:载入动态符号表地址
  • LC_LOAD_DYLINKER:加载动态加载库,可以看出示例使用的是/usr/lib/dyld
  • LC_UUID:确定文件的唯一标识,crash解析中也会有这个,去检测dysm文件和crash文件是否匹配
  • LC_VERSION_MIN_MACOSX/LC_VERSION_MIN_IPHONEOS:确定二进制文件要求的最低操作系统版本
  • LC_SOURCE_VERSION:构建该二进制文件使用的源代码版本
  • LC_MAIN:设置程序主线程的入口地址和栈大小
  • LC_ENCRYPTION_INFO_64:获取加密信息
  • LC_LOAD_DYLIB:加载额外的动态库
  • LC_FUNCTION_STARTS:定义一个函数起始地址表,使调试器和其他程序易于看到一个地址是否在函数内
  • LC_DATA_IN_CODE:定义在代码段内的非指令的表
  • LC_CODE_SIGNATURE:获取应用签名信息

    3.Data、链接信息

      如果说前面两部分的主要作用,是让kern内核知道如何读取MachO文件,并指定MachO文件的动态链接器(dyly)用来完成后续的动态库加载,然后设置好程序入口等一些列程序启动前的信息,那么Data和链接信息部分,就相当于当程序运行起来后,为每一个映射到虚拟内存中的指令操作提供真实的物理地址支持。详细的过程会面会单独写一篇文章展开来讲。

收获——熟悉MachO可以做什么

  理解原理很重要,了解MachO格式的结构和加载运行,不仅可以帮助我们理解MacOS和iOS的app可执行文件启动过程,还能做且不限于:

  • bitcode分析
  • crash符号化
  • 符号模块查找
  • 非OC函数switch
  • 包支持架构分析
  • 常量字符串分析
  • 进程启动速度优化
  • 学习经典的数据结构

希望你有所收获!下篇我们主要围绕dyld讲动态加载过程,see you~


参考文章:

  • 趣探 Mach-O:文件格式分析
  • Mach-O文件格式和程序从加载到执行过程
小可长江 wechat
更多最新分享请扫码关注我的微信公众号获取 🍻
0%